/**
 *
 */
package com.demo.serial;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

import gnu.io.*;

/**
 * Java使用RXTX进行串口通信
 *
 * @author javagoboy
 * @time 2017年3月25日 下午5:47:14
 */
public class ReadSerialPort implements Runnable, SerialPortEventListener {
    private final String APP_NAME = "JavaRXTXAppDemo";
    private final int TIMEOUT = 2000;// 打开串口等待的时间
    private int THREAD_TIME = 0;//关闭串口等待的时间（秒）
    private CommPortIdentifier commPort;
    private SerialPort serialPort;
    private InputStream inputStream;
    private OutputStream outputStream;

    /**
     * 获取所有可用的串口
     *
     * @author javagoboy
     * @time 2017年3月25日 下午5:57:12
     * @return
     */
    @SuppressWarnings("rawtypes")
    private Map<String, CommPortIdentifier> getAllUsefullPorts() {
        Enumeration en = CommPortIdentifier.getPortIdentifiers();
        System.out.println("All Usefull Port For This PC:" + en);

        Map<String, CommPortIdentifier> cpidMap = new HashMap<String, CommPortIdentifier>();
        while (en.hasMoreElements()) {
            CommPortIdentifier commPort = (CommPortIdentifier) en.nextElement();
            if (commPort.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                System.out.println(commPort.getName() + ", " + commPort.getCurrentOwner());
                cpidMap.put(commPort.getName(), commPort);
            }
        }

        return cpidMap;
    }

    /**
     * 根据串口名称获取CommPortIdentifier
     *
     * @author javagoboy
     * @time 2017年3月25日 下午6:03:11
     * @param comName
     *            串口名称
     * @return
     */
    public void selectPort(String comName) {
        Map<String, CommPortIdentifier> cpidMap = getAllUsefullPorts();
        for (Map.Entry<String, CommPortIdentifier> commPort : cpidMap.entrySet()) {
            if (comName != null && comName.equals(commPort.getKey())) {
                this.commPort = commPort.getValue();
            }
        }
    }

    /**
     *  打开串口，实例化SerialPort对象
     * @author javagoboy
     * @time 2017年3月25日 下午10:42:27
     */
    public void openPort() {
        if (commPort == null) {
            System.out.println("没有找到可用串口！");
        } else {
            System.out.println("成功开启串口，当前串口为：" + commPort.getName());
            try {
                System.out.println("开始实例化SerialPort，请稍等......");
                Thread.sleep(1000);
                serialPort = (SerialPort) commPort.open(APP_NAME, TIMEOUT);

                System.out.println("成功实例化SerialPort:" + commPort.getName());
            } catch (InterruptedException e) {
                System.out.println("程序出现异常！" + e.getCause());
            } catch (PortInUseException e) {
                System.out.println("实例化SerialPort失败，串口：" + commPort.getName() + "正在使用中......" + e.getCause());
            }
        }
    }

    public boolean checkPort() {
        if(commPort == null || serialPort == null) {
            System.out.println("串口未开启成功！");
            return false;
        }

        return true;
    }

    /**
     * 向端口发送数据<br>
     * 说明：调用此方法前需先选择端口，并确保SerialPort正常打开
     *
     * @author javagoboy
     * @time 2017年3月25日 下午10:06:32
     * @param message
     */
    public void sendMsg(String message) {
        checkPort();
        try {
            outputStream = new BufferedOutputStream(serialPort.getOutputStream());
            outputStream.write(message.getBytes());
            System.out.println("向串口："+commPort.getName()+"中写入Msg："+message);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("读取串口输出流失败......" + e.getCause());
        } finally {
            try {
                outputStream.close();
            } catch (Exception e) {
                System.out.println("关闭串口输出流失败......" + e.getCause());
            }
        }
    }

    /**
     * 开始监听从端口中接收的数据
     *
     * @author javagoboy
     * @time 2017年3月25日 下午10:21:54
     * @param second
     *            监听程序的存活时间，单位为秒，0 则是一直监听
     */
    public void readMsg(int second) {
        checkPort();
        try {
            inputStream = new BufferedInputStream(serialPort.getInputStream());
            serialPort.addEventListener(this);
        } catch (IOException | TooManyListenersException e) {
            throw new RuntimeException("获取端口的输入流异常：" + e.getMessage());
        }

        serialPort.notifyOnDataAvailable(true);

        System.out.println("开始监听串口：" + commPort.getName() + "的数据......");

        if (second > 0) {
            this.THREAD_TIME = second * 1000;
            Thread thread = new Thread(this);
            thread.start();
            System.out.println("串口监听程序将在" + second + "秒后关闭......");
        }
    }

    /**
     * 数据接收的监听处理函数
     */
    @Override
    public void serialEvent(SerialPortEvent arg0) {
        switch (arg0.getEventType()) {
            case SerialPortEvent.BI:// Break interrupt,通讯中断
            case SerialPortEvent.OE:// Overrun error，溢位错误
            case SerialPortEvent.FE:// Framing error，传帧错误
            case SerialPortEvent.PE:// Parity error，校验错误
            case SerialPortEvent.CD:// Carrier detect，载波检测
            case SerialPortEvent.CTS:// Clear to send，清除发送
            case SerialPortEvent.DSR:// Data set ready，数据设备就绪
            case SerialPortEvent.RI:// Ring indicator，响铃指示
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:// Output buffer is empty，输出缓冲区清空
                break;
            case SerialPortEvent.DATA_AVAILABLE:// Data available at the serial port，端口有可用数据。读到缓冲数组，输出到终端
                byte[] buffer = new byte[1024];
                String readMsg = "";
                try {
                    while (inputStream.available() > 0) {
                        inputStream.read(buffer);
                        readMsg += new String(buffer).trim();
                    }
                    System.out.println("接收到端口返回数据(长度为" + readMsg.length() + ")：" + readMsg);
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("读取串口输入流失败......" + e.getCause());
                }
        }
    }

    @Override
    public void run() {
        try {
            Thread.sleep(THREAD_TIME);
            serialPort.close();
            System.out.println("串口：" + commPort.getName() + "监听已经关闭了！");
        } catch (Exception e) {
            System.out.println("关闭串口监听失败......" + e.getCause());
        } finally {
            try {
                inputStream.close();
            } catch (Exception e) {
                System.out.println("关闭串口输入流失败......" + e.getCause());
            }
        }
    }

    public void close() {
        serialPort.close();
        serialPort = null;
        commPort = null;
    }




    public static void listPorts(){
        HashSet<CommPortIdentifier> portSet = getAvailableSerialPorts();
        for(CommPortIdentifier comm : portSet){
            System.out.println(comm.getName()  +  " - " +  getPortTypeName(comm.getPortType()) );
        }
    }



    /**
     * @Description:获取所有可用的串口集合
     * @author:Lu
     * @date:2015-8-29 上午11:37:54
     */
    @SuppressWarnings("unchecked")
    public static HashSet<CommPortIdentifier> getAvailableSerialPorts() {
        HashSet<CommPortIdentifier> h = new HashSet<CommPortIdentifier>();
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        while (portList.hasMoreElements()) {
            CommPortIdentifier com = (CommPortIdentifier) portList
                    .nextElement();
            switch (com.getPortType()) {
                case CommPortIdentifier.PORT_SERIAL:
                    try {
                        //open:（应用程序名【随意命名】，阻塞时等待的毫秒数）
                    /*open方法打开通讯端口，获得一个CommPort对象，它使程序独占端口。
                    如果端口正被其他应用程序占用，将使用CommPortOwnershipListener事件机制
                    传递一个PORT_OWNERSHIP_REQUESTED事件。
                    每个端口都关联一个InputStream和一个OutputStream,如果端口是用
                    open方法打开的，那么任何的getInputStream都将返回相同的数据流对象，除非
                    有close被调用。
                    */
                        CommPort thePort = com.open(Object.class.getSimpleName(), 50);
                        thePort.close();
                        h.add(com);
                    } catch (PortInUseException e) {
                        //不可用串口
                        e.printStackTrace();
                        System.out.println("Port, " + com.getName() + ", is in use.");
                    } catch (Exception e) {
                        System.err.println("Failed to open port " + com.getName());
                        e.printStackTrace();
                    }
            }
        }
        return h;
    }


    /**
     * @Description:获取通信端口类型名称
     * @author:Lu
     * @date:2015-8-29 上午11:35:32
     */
    public static String getPortTypeName(int portType) {
        switch (portType) {
            case CommPortIdentifier.PORT_I2C:
                return "I2C";
            case CommPortIdentifier.PORT_PARALLEL: // 并口
                return "Parallel";
            case CommPortIdentifier.PORT_RAW:
                return "Raw";
            case CommPortIdentifier.PORT_RS485: // RS485端口
                return "RS485";
            case CommPortIdentifier.PORT_SERIAL: // 串口
                return "Serial";
            default:
                return "unknown type";
        }
    }



    /**
     * @Description:列出所有通信端口
     * @author:Lu
     * @date:2015-8-29 下午2:06:17
     */
    @SuppressWarnings("unchecked")
    public static void listCommPorts(){
        CommPortIdentifier.getPortIdentifiers();
        /*不带参数的getPortIdentifiers方法可以获得一个枚举对象，该对象包含了
         系统中每个端口的CommPortIdentifier对象。注意这里的端口不仅仅是指串口，也包括并口。
         这个方法还可以带参数，getPortIdentifiers(CommPort)获得已经被应用程序打开的端口
         相对应的CommPortIdentifier对象。getPortIdentifier(String portName)
         获取指定端口名（比如“COM1”）的CommPortIdentifier对象。*/
        java.util.Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers();
        while (portEnum.hasMoreElements()) {
            CommPortIdentifier portIdentifier = portEnum.nextElement();
            System.out.println(portIdentifier.getName() + " -0000- " + getPortTypeName(portIdentifier.getPortType()));
        }
    }



    public static void main(String[] args) {


        /**
         * 可列出当前系统所有可用的串口名称，本机输出COM1 - Serial， COM2 - Serial.....COM6 - Serial
         */
        /*listPorts();
        listCommPorts();*/


        new ReadSerialPort().getAllUsefullPorts().forEach((K,V) -> System.out.println(K +"   "+ V.getName()));

        /*String comName = "Bluetooth";
        String message = "Hello,Serial Port";

        ReadSerialPort rsp = new ReadSerialPort();
        rsp.selectPort(comName);
        rsp.openPort();
        rsp.sendMsg(message);
        rsp.readMsg(0);*/
    }
}
